import { Injectable } from '@nestjs/common';
import { CreateUserDto, FindUserDto, UpdateUserDto, RemoveUserDto, RemoveBatchUserDto, LoginUserDto } from './dto/user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { plainToInstance } from 'class-transformer';
import { CaptchaTypeEnum, CaptchaVerifyEnum } from '@/common/enums';
import { IoredisService } from '../ioredis/ioredis.service';
import * as crypto from 'crypto';
import { AuthService } from '../auth/auth.service';
import { ExtraContext } from '@/common/interfaces/extracontext.interface';
import { CreateTokenDto } from '../auth/dto/auth.dto';
import { RoleService } from '../role/role.service';
import { isTruthy } from '@/common/utils/judge';

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private readonly user_db: Repository<User>,
    private readonly ioredisService: IoredisService,
    private readonly authService: AuthService,
    private readonly roleService: RoleService
  ) {}

  async login(data: LoginUserDto, extra: ExtraContext) {
    const { ip } = extra;

    // 图形验证码校验
    const gcaptchaRedisKey = `captcha:${CaptchaTypeEnum.Graphics}:${CaptchaVerifyEnum.Login}:${ip}`;
    const gcaptchaRedis = await this.ioredisService.get(gcaptchaRedisKey);
    if (!gcaptchaRedis) {
      return {
        code: 400,
        msg: '请先获取图形验证码'
      };
    }
    if (data.gcaptcha?.toLowerCase() !== gcaptchaRedis?.toLowerCase()) {
      return {
        code: 400,
        msg: '图形验证码错误'
      };
    }

    // 账号校验
    const oneUser = await this.user_db.findOne({ where: { username: data.username } });
    if (!oneUser) {
      return {
        code: 400,
        msg: '用户不存在'
      };
    }

    // 密码校验
    if (!data?.password) {
      return {
        code: 400,
        msg: '请输入密码'
      };
    }
    const cryptoPassword = crypto.createHash('sha256').update(data.password).digest('hex');
    if (cryptoPassword !== oneUser.password) {
      return {
        code: 400,
        msg: '密码错误'
      };
    }

    // 登录成功
    const loginSuccessRes = await this.loginSuccess(oneUser, extra);

    // 删除缓存中的图形验证码
    await this.ioredisService.del(gcaptchaRedisKey);

    return {
      ...loginSuccessRes,
      msg: '登录成功'
    };
  }

  async loginSuccess(user: User, extra: ExtraContext) {
    const { ip } = extra;

    // 登录成功，身份缓存，生成token
    const identityAttr: CreateTokenDto = {
      id: user.id,
      username: user.username,
      roles: user.roles
    };

    await this.authService.identityCache(identityAttr);

    const { access_token, refresh_token } = this.authService.createToken(identityAttr);

    // 更新参数
    const updatedata = {
      loginTime: new Date(),
      loginIp: ip
    };

    // 更新并查询最新数据
    await this.user_db.update({ id: user.id }, updatedata);
    const newOne = await this.user_db.findOne({ where: { id: user.id } });

    // 移除指定不可见字段
    delete newOne.password;
    delete newOne.roles;

    return {
      data: newOne,
      access_token,
      refresh_token
    };
  }

  async register(data: CreateUserDto, extra: ExtraContext) {
    const { ip } = extra;

    // 图形验证码校验
    const gcaptchaRedisKey = `captcha:${CaptchaTypeEnum.Graphics}:${CaptchaVerifyEnum.Register}:${ip}`;
    const gcaptchaRedis = await this.ioredisService.get(gcaptchaRedisKey);
    if (!gcaptchaRedis) {
      return {
        code: 400,
        msg: '请先获取图形验证码'
      };
    }
    if (data.gcaptcha?.toLowerCase() !== gcaptchaRedis?.toLowerCase()) {
      return {
        code: 400,
        msg: '图形验证码错误'
      };
    }

    // 按需决定是否需要邮箱验证码
    let ecaptchaRedisKey = '';
    if (data.needecaptcha) {
      // 邮箱验证码校验
      ecaptchaRedisKey = `captcha:${CaptchaTypeEnum.Email}:${CaptchaVerifyEnum.Register}:${ip}:${data.email}`;
      const ecaptchaRedis = await this.ioredisService.get(ecaptchaRedisKey);
      if (!ecaptchaRedis) {
        return {
          code: 400,
          msg: '请先获取邮箱验证码'
        };
      }
      if (data.ecaptcha?.toLowerCase() !== ecaptchaRedis?.toLowerCase()) {
        return {
          code: 400,
          msg: '邮箱验证码错误'
        };
      }
    }

    // 密码校验
    if (data.password !== data.password2) {
      return {
        code: 400,
        errMsg: '密码不一致',
        msg: '注册失败'
      };
    }

    // 当数据库中没有用户时，创建admin账号，且该账号无法删除
    const usernum = await this.user_db.count();
    if (usernum == 0) {
      data.roles = ['admin'];
    } else {
      data.roles = ['user'];
    }

    // 检测username是否重复
    const repeatUser = await this.user_db.findOne({ where: { username: data.username } });
    if (repeatUser) {
      return {
        code: 400,
        errMsg: '用户名已存在',
        msg: '注册失败'
      };
    }

    // 密码加密
    data.password = crypto.createHash('sha256').update(data.password).digest('hex');

    const entity = plainToInstance(User, data);
    // 部分其他默认属性
    entity.registerIp = ip;
    entity.registerTime = new Date();

    // 注册成功保存用户数据
    const oneUser = await this.user_db.save(entity);

    // 注册完成后是否自动登录
    if (data.autologin) {
      // 注册成功后自动完成登录
      const loginSuccessRes = await this.loginSuccess(oneUser, extra);

      return {
        ...loginSuccessRes,
        msg: '注册并登录成功'
      };
    }

    // 注册成功后删除缓存中的验证码
    await this.ioredisService.dels([gcaptchaRedisKey, ecaptchaRedisKey]);

    return {
      msg: '注册成功'
    };
  }

  async findAll(data: FindUserDto) {
    const queryBuilder = this.user_db.createQueryBuilder('user');

    // 选择字段：过滤password
    queryBuilder.select([
      'user.id',
      'user.username',
      'user.nickname',
      'user.avatar',
      'user.email',
      'user.phone',
      'user.remark',
      'user.gender',
      'user.roles',
      'user.tags',
      'user.department',
      'user.signature',
      'user.status',
      'user.birthday',
      'user.registerTime',
      'user.registerIp',
      'user.loginTime',
      'user.loginIp',
      'user.createTime',
      'user.updateTime'
    ]);

    // 查询条件
    if (data.id) queryBuilder.andWhere('user.id = :id', { id: data.id });
    if (data.username) queryBuilder.andWhere('user.username = :username', { username: data.username });
    if (data.nickname) queryBuilder.andWhere('user.nickname LIKE :nickname', { nickname: `%${data.nickname}%` });
    if (data.role) queryBuilder.andWhere('FIND_IN_SET(:role, user.roles) > 0', { role: data.role });
    if (data.department) queryBuilder.andWhere('user.department = :department', { department: data.department });
    // 数字类型特殊处理
    if (isTruthy(data.status, 'zero')) queryBuilder.andWhere('user.status = :status', { status: String(data.status) });

    // 添加排序
    queryBuilder.orderBy(`user.${data.sortField}`, data.sortOrder);
    // pageSize 为正整数时，添加分页
    data.pageSize > 0 && queryBuilder.skip((data.pageNum - 1) * data.pageSize).take(data.pageSize);
    // 执行查询
    const [res, total] = await queryBuilder.getManyAndCount();
    // 总页数
    const pages = data.pageSize > 0 ? Math.ceil(total / data.pageSize) : 1;

    return {
      data: res,
      pageNum: data.pageNum,
      pageSize: data.pageSize,
      pages,
      total,
      msg: '查询成功'
    };
  }

  async findOne(data: FindUserDto) {
    const queryBuilder = this.user_db.createQueryBuilder('user');

    // 选择字段：过滤password
    queryBuilder.select([
      'user.id',
      'user.username',
      'user.nickname',
      'user.avatar',
      'user.email',
      'user.phone',
      'user.remark',
      'user.gender',
      'user.roles',
      'user.tags',
      'user.department',
      'user.signature',
      'user.status',
      'user.birthday',
      'user.registerTime',
      'user.registerIp',
      'user.loginTime',
      'user.loginIp',
      'user.createTime',
      'user.updateTime'
    ]);

    // 必须指定查询条件
    if (!data.id && !data.username) {
      return {
        code: 400,
        msg: '查询条件不能为空'
      };
    }

    // 只查询主键或唯一键
    if (data.id) queryBuilder.andWhere('user.id = :id', { id: data.id });
    if (data.username) queryBuilder.andWhere('user.username = :username', { username: data.username });

    // 执行查询
    const res = await queryBuilder.getOne();

    return {
      data: res,
      msg: '查询成功'
    };
  }

  async hasAdmin() {
    const queryBuilder = this.user_db.createQueryBuilder('user');

    // 选择字段：过滤password
    queryBuilder.select(['user.id', 'user.username', 'user.roles']);

    // 查询条件
    queryBuilder.andWhere('FIND_IN_SET(:role, user.roles) > 0', { role: 'admin' });

    // 执行查询
    const count = await queryBuilder.getCount();

    return {
      data: count > 0,
      msg: '查询成功'
    };
  }

  async update(data: UpdateUserDto) {
    const queryBuilder = this.user_db.createQueryBuilder().update();

    // 只查询主键
    queryBuilder.where('id = :id', { id: data.id });

    // 更新数据
    queryBuilder.set(data);

    // 执行更新
    const res = await queryBuilder.execute();

    if (res.affected > 0) {
      return {
        msg: '更新成功'
      };
    } else {
      return {
        code: 400,
        msg: '无有效数据更新'
      };
    }
  }

  async remove(data: RemoveUserDto) {
    const queryBuilder = this.user_db.createQueryBuilder().delete();

    // 只查询主键
    queryBuilder.where('id = :id', { id: data.id });

    // 执行删除
    const res = await queryBuilder.execute();

    if (res.affected > 0) {
      return {
        msg: '删除成功'
      };
    } else {
      return {
        code: 400,
        msg: '无有效数据删除'
      };
    }
  }

  async removeBatch(data: RemoveBatchUserDto) {
    let { ids } = data;
    const mainkey = 'id'; // 主键
    let clash = []; // 冲突的主键明细
    let romoved = 0; // 删除成功的数量

    try {
      await this.user_db.manager.transaction(async (manager) => {
        if (ids.length == 0) {
          throw new Error('ids 为空，无数据删除');
        }

        const repManager = manager.getRepository(User);
        const deleteRes = await repManager.delete({ id: In(ids) });
        romoved = deleteRes.affected;

        if (romoved == 0) {
          throw new Error('无有效数据删除');
        }

        // 如果有未受影响的行，说明有些ID删除失败
        if (ids.length !== romoved) {
          // 查询仍存在的ID
          const remains = await repManager.find({ where: { [mainkey]: In(ids) } });
          clash = remains.map((item) => item[mainkey]);

          if (clash.length > 0) {
            // 抛出异常，阻止整个事务的提交
            throw new Error(`删除失败 ids: ${clash.join(',')}`);
          }
        }
      });

      return {
        msg: '批量删除成功',
        tip: romoved
      };
    } catch (error) {
      return {
        code: 400,
        errMsg: error.message,
        msg: '批量删除失败'
      };
    }
  }
}
